Skip to content

Rolling integrity queue, time budgets, bitrot detection (v2.6.58-62)#62

Merged
ttlequals0 merged 4 commits into
mainfrom
feature/rolling-integrity-bitrot
Jul 10, 2026
Merged

Rolling integrity queue, time budgets, bitrot detection (v2.6.58-62)#62
ttlequals0 merged 4 commits into
mainfrom
feature/rolling-integrity-bitrot

Conversation

@ttlequals0

Copy link
Copy Markdown
Owner

Summary

  • Integrity checks run as a rolling stalest-first queue: batched fetches ordered on last_integrity_check_date replace the single full-table load, every terminal outcome (success, hash error, abandoned task, unreadable result) stamps the cursor in bulk, and interrupted runs resume where they left off. In-flight, stamp-failed, and dispatch-failed rows are excluded from re-fetches so the producer always terminates.
  • file_changes schedules accept time_budget_minutes (NULL = unlimited): a soft deadline that stops new dispatch, drains in-flight hashes, and reports coverage. Budgeted batches dispatch largest-first with saturated-tier skip-ahead so huge files start early without capping concurrency. Manual runs stay unlimited unless the request passes a budget.
  • Hash mismatches are classified instead of uniformly marked modified: content change with unchanged mtime (2 s epsilon) is suspected bitrot. Flagged files keep their stored hash/mtime baseline through every scan writer (single shared apply_scan_baseline guard, used by both the serial save path and the parallel chunk workers), jump the queue, auto-expire after BITROT_STABLE_CHECKS_TO_EXPIRE stable checks plus a clean rescan, and can be accepted manually via POST /api/bitrot/accept. Detection date/details are permanent.
  • Stored mtimes are written as UTC with a per-row mtime_baseline_utc trust flag; pre-upgrade naive-local rows re-baseline on their first check (hash-match rows re-baseline in place, mismatch rows via rescan) so old data can never produce false bitrot alarms. Scan-cache validity now keys on content-hash equality so the UTC change cannot force a full-library re-decode.
  • Notification rules actually fire now: new dispatch_event() joins active NotificationRule rows to active providers; bitrot_suspected sends one aggregated high-priority notification per run.
  • UI: bitrot badge/filter/detail panel with accept action, schedule-form budget field, coverage in run reports. CSV/JSON exports and /api/scan-results gain bitrot fields/filter.
  • Removed the dead serial MediaChecker.check_file_changes path and a per-run AsyncResult accumulation leak; added a composite index matching the queue ordering.

Version

2.6.58 through 2.6.62 (see CHANGELOG.MD for per-version detail).

Note for deploy: the v2.6.61 migration adds six columns and two indexes to scan_results; the index build briefly holds the migration advisory lock at startup on large tables.

Test plan

  • Full suite: 452 passed, 0 failed, 8 skipped (8 pre-existing 60 s fixture timeouts in test_real_media_samples.py on slow local hardware, present on main)
  • 60+ new unit/integration tests: queue ordering and disjoint sweeps, budget validation, classification matrix incl. already-flagged branch and epsilon boundaries, baseline guard helpers, accept endpoint, notification dispatcher
  • Webpack production build
  • Docker image built and pushed
  • Deployed and version verified

….62)

Integrity checks now sweep the library as a rolling stalest-first queue
(batched fetches keyed on last_integrity_check_date) instead of a single
all-or-nothing full-table pass, resume after crash or cancellation by
construction, and stamp every terminal outcome in bulk. file_changes
schedules can carry a per-run time budget (soft deadline, drain on expiry,
saturated-tier skip-ahead) so successive runs cover the library in slices.

Hash mismatches are now classified: changed mtime = legitimate modification,
unchanged mtime = suspected bitrot. Flagged files keep their stored baseline
through every scan writer (shared apply_scan_baseline guard), jump the
integrity queue, auto-expire after consecutive stable checks plus a clean
rescan, and can be manually accepted via POST /api/bitrot/accept. One
aggregated high-priority notification fires per run through the new
NotificationRule dispatcher. Stored mtimes are written as UTC with a per-row
trust flag; untrusted pre-upgrade rows re-baseline on their first check.

Removes the dead serial MediaChecker.check_file_changes path and a per-run
AsyncResult accumulation leak, and adds a composite index for the queue
ordering.
Schedule create/update/delete dispatched reload_schedules_task via Celery,
but prefork workers execute tasks in pool children while APScheduler runs
only in the scheduler-lock-holding main process. The reload always landed
in a child and was skipped, so schedule changes never reached the running
scheduler without a container restart.

The scheduler process now re-syncs its jobs from the database every 60
seconds, gated on a fingerprint of schedule definitions so jobs are only
rebuilt on actual change. next_run is computed at schedule creation so the
UI shows the upcoming fire before the first run.
Budgeted integrity runs sweep the library in slices, so no single run
report answers whether every file has been verified. /api/stats now
returns an integrity block: files checked ever and in the last 30 days,
coverage percent, never-checked count, oldest check date, and current
bitrot-suspected count. The dashboard gains an Integrity Checked stat
card with the full breakdown in its tooltip.
@ttlequals0 ttlequals0 merged commit 65787f1 into main Jul 10, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant